package medOffice;

import java.io.Serializable;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import medOffice.Enuns.TipoDeConsulta;

/**
 * Classe filha de Pessoa que implementa um Medico
 * 
 * @author Ana Clara Lacerda anacls@lcc.ufcg.edu.br
 * @version 1.0 - 04/12/2009
 */
public class Medico extends Pessoa implements Serializable {

	private static final long serialVersionUID = -1197668489620287690L;
	private List<PlanoDeSaude> planosDeSaude;
	private Time tempoConsulta;
	private Time tempoRetorno;
	private Map<Integer, List<Time[]>> horariosDisponiveis;
	private Calendar ultimoHorarioOcupado;

	/**
	 * Construtor default de Medico.
	 */
	public Medico() {
		horariosDisponiveis = new HashMap<Integer, List<Time[]>>();
		planosDeSaude = new ArrayList<PlanoDeSaude>();

	}

	/**
	 * Construtor do Medico
	 * 
	 * @param String
	 *            Nome do medico
	 * @param String
	 *            CPF do medico
	 * @throws Exception
	 *             Caso o nome e/ou CPF sejam invalidos
	 */
	public Medico(String nome, String cpf) throws Exception {
		super(nome, cpf);
		horariosDisponiveis = new HashMap<Integer, List<Time[]>>();
		planosDeSaude = new ArrayList<PlanoDeSaude>();
	}

	/**
	 * Metodo acessador que retorna a lista de Planos de Saude
	 * aceitos pelo Medico
	 * 
	 * @return List<PlanoDeSaude> lista de planos
	 */
	public List<PlanoDeSaude> getPlanosDeSaude() {
		return planosDeSaude;
	}

	/**
	 * Metodo modificador que seta os planos de saude do medico.
	 * 
	 * @param List<PlanoDeSaude>
	 *            a nova Lista de Planos
	 * @throws Exception
	 *             caso a nova lista seja null
	 */
	public void setPlanosDeSaude(List<PlanoDeSaude> planosDeSaude)
			throws Exception {
		if (planosDeSaude == null)
			throw new Exception("Lista de planos invalida");
		this.planosDeSaude = planosDeSaude;
	}

	/**
	 * Metodo acessador que retorna o tempo de consulta do Medico
	 * 
	 * @return Time 
	 * 			  tempo da consulta
	 */
	public Time getTempoConsulta() {
		return tempoConsulta;
	}

	/**
	 * Metodo modificador que seta tempo de consulta
	 * 
	 * @param Time
	 *            novo tempo de consulta
	 */
	public void setTempoConsulta(Time tempoConsulta) {
		this.tempoConsulta = tempoConsulta;
	}

	/**
	 * Metodo modificador que seta o tempo de consulta
	 * 
	 * @param String 
	 *            novo tempo de consulta ( String no formato hh:mm:ss )
	 * @throws Exception
	 *             caso a string esteja no formato errado
	 */
	public void setTempoConsulta(String tempo) throws Exception {
		if (!(Verificador.verificaTempo(tempo)))
			throw new Exception("Tempo invalido");
		Time novoTempo = Time.valueOf(tempo);
		setTempoConsulta(novoTempo);
	}

	/**
	 * Metodo acessador que retorna o tempo de retorno do Medico
	 * 
	 * @return Time 
	 * 			  tempo de retorno
	 */
	public Time getTempoRetorno() {
		return tempoRetorno;
	}

	/**
	 * Metodo modificador que seta o tempo de retorno
	 * 
	 * @param Time
	 *            novo tempo de retorno
	 */
	public void setTempoRetorno(Time tempoRetorno) {
		this.tempoRetorno = tempoRetorno;
	}

	/**
	 * Metodo modificador que seta o tempo de retorno
	 * 
	 * @param String
	 *            novo tempo de retorno ( String no formato hh:mm:ss )
	 * @throws Exception
	 *             caso a string esteja no formato errado
	 */
	public void setTempoRetorno(String tempo) throws Exception {
		if (!(Verificador.verificaTempo(tempo)))
			throw new Exception("Tempo invalido");
		Time novoTempo = Time.valueOf(tempo);
		setTempoRetorno(novoTempo);
	}

	/**
	 * Metodo acessador que retorna os horarios em que o Medico esta na clinica
	 * 
	 * @return Map<Integer, List<Time[]>>
	 * 		   Um mapa que possui: chave - umInteger que representa o dia da
	 *         semana; valores- listas de arrays de Time(tamanho 2) que
	 *         representa os horarios de entrada e saida
	 */
	public Map<Integer, List<Time[]>> getHorariosDisponiveis() {
		return horariosDisponiveis;
	}

	/**
	 * Metodo acessador que retorna os horarios disponiveis do medico 
	 * de algum dia especifico.
	 * 
	 * @param Integer
	 *            Integer que representa um dia especifico
	 * @return List<Time[]>
	 * 					uma lista de arrays de Time[tamanho 2]
	 * @throws Exception
	 *             caso o dia seja invalido (nao seja um numero entre 1 e 7 ou a
	 *             lista de horarios nao contenha o dia)
	 */
	public List<Time[]> getHorariosDoDia(Integer dia) throws Exception {
		if (Verificador.verificaInteiroEntre(dia, 7, 1)
				&& getHorariosDisponiveis().containsKey(dia))
			return getHorariosDisponiveis().get(dia);
		throw new Exception("Dia invalido");
	}

	/**
	 * Metodo modificador que seta os horarios disponiveis do Medico
	 * 
	 * @param Map<Integer, List<Time[]>>
	 * 				 horariosDisponiveis Mapa de horarios disponiveis
	 * @throws Exception
	 *             caso o parametro seja null ou os dias da semana nao sejam
	 *             validos
	 */
	public void setHorariosDisponiveis(
			Map<Integer, List<Time[]>> horariosDisponiveis) throws Exception {
		boolean valido = true;
		for (Integer dia : horariosDisponiveis.keySet()) {
			if (!(Verificador.verificaInteiroEntre(dia, 7, 1))) {
				valido = false;
				break;
			}
			for (Time[] t : horariosDisponiveis.get(dia)) {
				if (t.length != 2) {
					valido = false;
					break;
				}
			}
			break;
		}
		if (horariosDisponiveis == null || !valido)
			throw new Exception("Horarios invalidos");
		this.horariosDisponiveis = horariosDisponiveis;
	}

	/**
	 * Metodo acessador que retorna o ultimo horario ocupado do medico
	 * 
	 * @return Calendar 
	 * 				ultimo horario ocupado
	 */
	public Calendar getUltimoHorarioOcupado() {
		return ultimoHorarioOcupado;
	}

	/**
	 * Metodo modificador que seta o ultimo Horario ocupado do medico
	 * 
	 * @param Calendar 
	 * 				ultimo horario ocupado novo horario
	 */
	public void setUltimoHorarioOcupado(Calendar ultimoHorarioOcupado) {
		this.ultimoHorarioOcupado = ultimoHorarioOcupado;
	}

	/**
	 * Metodo que adiciona um horario a um dia especifico
	 * 
	 * @param Integer
	 *            inteiro que representa o dia
	 * @param Time[]
	 *            Um array de Time de tamanho 2
	 * @throws Exception
	 *             caso o tamanho do array nao seja 2, ou o dia seja invalido
	 */
	public void adicionaHorarioDisponivel(Integer dia, Time[] horario)
			throws Exception {
		if (horario.length != 2)
			throw new Exception("Horario invalido");
		if (!(getHorariosDisponiveis().containsKey(dia)))
			adicionaDia(dia);
		getHorariosDisponiveis().get(dia).add(horario);
	}

	/**
	 * Metodo que adiciona um dia no mapa e inicializa sua lista de horarios
	 * 
	 * @param Integer
	 *            inteiro que representa o dia a ser adicionado
	 * @throws Exception
	 *             caso o dia seja invalido ( nao esteja entre 1 e 7 )
	 */
	private void adicionaDia(Integer dia) throws Exception {
		if (!(Verificador.verificaInteiroEntre(dia, 7, 1)))
			throw new Exception("Dia da semana invalido");
		getHorariosDisponiveis().put(dia, new ArrayList<Time[]>());

	}

	/**
	 * Metodo acessador que retorna o horario de saida do Medico
	 * em um determinado dia.
	 * 
	 * @param Integer
	 *            inteiro que representa o dia da semana
	 * @return Time
	 * 			  horario de saida
	 * @throws Exception
	 *             caso o dia seja invalido
	 */
	public Time getHorarioSaidaDia(Integer dia) throws Exception {
		Time horario = new Time(0);
		if (Verificador.verificaInteiroEntre(dia, 7, 1)
				&& getHorariosDisponiveis().containsKey(dia)) {
			for (Time[] t : getHorariosDoDia(dia)) {
				if (t[1].compareTo(horario) >= 0)
					horario = t[1];
			}
			return horario;
		}
		throw new Exception("Dia invalido");
	}

	/**
	 * Metodo acessador que retorna o horario de chegada do medico em um determinado dia
	 * 
	 * @param Integer
	 *            inteiro que representa o dia da semana
	 * @return Time 
	 * 			  horario de chegada
	 * @throws Exception
	 *             caso o dia seja invalido
	 */
	public Time getHorarioChegadaDia(Integer dia) throws Exception {
		if (Verificador.verificaInteiroEntre(dia, 7, 1)
				&& getHorariosDisponiveis().containsKey(dia)) {
			Time horario = getHorariosDoDia(dia).get(0)[0];
			for (Time[] t : getHorariosDoDia(dia)) {
				if (t[0].compareTo(horario) <= 0)
					horario = t[0];
			}
			return horario;
		}
		throw new Exception("Dia invalido");
	}

	/**
	 * Remove um dia inteiro dos horarios disponiveis do Medico
	 * 
	 * @param Integer
	 *            inteiro que representa o dia da semana
	 * @throws Exception
	 *             caso o dia seja invalido
	 */
	public void removeDiaDisponivel(Integer dia) throws Exception {
		if (Verificador.verificaInteiroEntre(dia, 7, 1)
				&& getHorariosDisponiveis().containsKey(dia))
			getHorariosDisponiveis().remove(dia);
		else
			throw new Exception("Dia invalido");
	}

	/**
	 * Remove um horario de um dia especifico
	 * 
	 * @param Integer
	 *            inteiro que representa o dia da semana
	 * @param Time[]
	 *            o horario que sera removido do dia especificado
	 * @throws Exception
	 *             caso o dia ou horario seja invalido
	 */
	public void removeHorarioDisponivel(Integer dia, Time[] horario)
			throws Exception {
		if (Verificador.verificaInteiroEntre(dia, 7, 1)
				&& getHorariosDisponiveis().containsKey(dia)
				&& getHorariosDisponiveis().get(dia).contains(horario))
			getHorariosDisponiveis().get(dia).remove(horario);
		else
			throw new Exception("Dia/Horario invalido");
	}

	/**
	 * Metodo que adiciona um plano de saude ao Medico
	 * 
	 * @param PlanoDeSaude
	 *            plano de saude que vai ser adicionado
	 * @throws Exception
	 *             Caso o medico ja possua esse plano de saude na lista
	 */
	public void adicionaPlanoDeSaude(PlanoDeSaude ps) throws Exception {
		if (!(Verificador.verificaPlanoDeSaude(this, ps)))
			throw new Exception("Plano de saude nao foi adicionado");
		getPlanosDeSaude().add(ps);
	}

	/**
	 * Metodo que remove um plano de saude da lista de planos
	 * 
	 * @param int
	 *            codigo do plano de saude a ser retornado
	 * @return PlanoDeSaude 
	 * 				plano de saude que foi removido, ou null caso o plano de saude
	 *         		nao tenha sido acahado na lista
	 */
	public PlanoDeSaude removePlanoDeSaude(int codigo) {
		for (PlanoDeSaude p : getPlanosDeSaude()) {
			if (p.getCodigo() == codigo) {
				getPlanosDeSaude().remove(p);
				return p;
			}
		}
		return null;
	}

	/**
	 * Metodo que retorna um mapa com chaves iguais ao de horario disponivel e
	 * com a duracao em minutos de cada horario de trabalho do medico
	 * 
	 * @return HashMap<Integer, ArrayList<Integer>>
	 */
	public HashMap<Integer, ArrayList<Integer>> calculaMinutosDiaHorario() {
		HashMap<Integer, ArrayList<Integer>> minutosHorario = new HashMap<Integer, ArrayList<Integer>>();
		Iterator<Integer> dias = getHorariosDisponiveis().keySet().iterator();
		while (dias.hasNext()) {
			int dia = dias.next();
			minutosHorario.put(dia, calculaMinutos(getHorariosDisponiveis()
					.get(dia)));
		}
		return minutosHorario;
	}

	/**
	 * Metodo que calcula o tempo em minutos de uma lista de horarios
	 * 
	 * @param List<Time[]> lista de horarios
	 * @return ArrayList<Integer> Array com todos os horarios.
	 */
	private ArrayList<Integer> calculaMinutos(List<Time[]> list) {
		ArrayList<Integer> minutos = new ArrayList<Integer>();
		for (Time[] horario : list) {
			Calendar horarioUm = new GregorianCalendar();
			Calendar horarioDois = new GregorianCalendar();
			horarioUm.setTime(horario[0]);
			horarioDois.setTime(horario[1]);
			minutos.add((horarioDois.get(Calendar.HOUR_OF_DAY) - horarioUm
					.get(Calendar.HOUR_OF_DAY))
					* 60
					+ (horarioDois.get(Calendar.MINUTE) - horarioUm
							.get(Calendar.MINUTE)));
		}
		return minutos;
	}

	/**
	 * Metodo que calcula e retorna a quantidade de minutos de cada tipo
	 * consulta
	 * 
	 * @param Enum TipoDeConsulta
	 *            Tipo de consulta, RETORNO ou CONSULTA
	 * @return int 
	 * 			  minutos do tipo especifico de consulta
	 */
	public int getTempoMinutos(TipoDeConsulta tipo) {
		Time temp = null;
		if (tipo.equals(TipoDeConsulta.RETORNO))
			temp = getTempoRetorno();
		else
			temp = getTempoConsulta();
		Calendar tempo = new GregorianCalendar();
		tempo.setTime(temp);
		return tempo.get(Calendar.MINUTE) + (tempo.get(Calendar.HOUR_OF_DAY)*60);
	}
	
	/**
	 * Metodo que calcula o valor em minutos do maior tempo gasto entre
	 * consulta e retorno
	 * @return int 
	 * 			tempo em minutos
	 */
	public int getMaxTempoMinutoConsulta(){
		return getTempoMinutos(TipoDeConsulta.CONSULTA) >= 
		getTempoMinutos(TipoDeConsulta.RETORNO)? 
			getTempoMinutos(TipoDeConsulta.CONSULTA): 
				getTempoMinutos(TipoDeConsulta.RETORNO); 
	}
	
	
	@Override
	public String toString() {
		return "Nome: " + getNome() + "\nCPF: " + getCPF()
				+ "Planos de Saude: " + getPlanosDeSaude();
	}

}
